package store

import (
	"dingusxp/trial/guestbook/store"
	"dingusxp/trial/guestbook/store/factory"
	"encoding/json"
	"errors"
	"fmt"
	"log"

	"github.com/go-redis/redis"
)

func init() {

	factory.Register("redis", &RedisStore{
		client:   nil,
		StoreKey: "guestbook",
	})
}

type RedisStore struct {
	client   *redis.Client
	StoreKey string
}

// helpers
func (rs *RedisStore) conn() error {

	if rs.client == nil {
		rs.client = redis.NewClient(&redis.Options{
			Addr: ":6379",
		})
		_, err := rs.client.Ping().Result()
		if err != nil {
			return errors.New("redis ping error: %s" + err.Error())
		}
	}
	return nil
}
func encodeNote(note store.Note) (string, error) {

	str, err := json.Marshal(note)
	if err != nil {
		return "", err
	}
	return string(str), nil
}
func decodeNote(str string) (store.Note, error) {

	var note store.Note
	if err := json.Unmarshal([]byte(str), &note); err != nil {
		return note, err
	}
	return note, nil
}
func id2field(id uint64) string {

	return fmt.Sprintf("%d", id)
}

// interfaces
func (rs *RedisStore) Create(note *store.Note) error {

	if err := rs.conn(); err != nil {
		log.Printf("redis conn failed: %s\n", err.Error())
		return store.ErrorEngineOperationError
	}

	_, err := rs.client.HGet(rs.StoreKey, id2field(note.Id)).Result()
	if err != redis.Nil {
		if err == nil {
			return store.ErrorDuplicated
		} else {
			log.Printf("[HGet] redis operation failed: %s\n", err.Error())
			return store.ErrorEngineOperationError
		}
	}

	value, err := encodeNote(*note)
	if err != nil {
		return store.ErrorEngineOperationError
	}

	if err := rs.client.HSet(rs.StoreKey, id2field(note.Id), value).Err(); err != nil {
		log.Printf("[HSet] redis operation failed: %s\n", err.Error())
		return store.ErrorEngineOperationError
	}

	return nil
}

func (rs *RedisStore) Get(id uint64) (store.Note, error) {

	if err := rs.conn(); err != nil {
		log.Printf("redis conn failed: %s\n", err.Error())
		return store.Note{}, store.ErrorEngineOperationError
	}

	value, err := rs.client.HGet(rs.StoreKey, id2field(id)).Result()
	if err == redis.Nil {
		return store.Note{}, store.ErrorNotFound
	} else if err != nil {
		log.Printf("[HGet] redis operation failed: %s\n", err.Error())
		return store.Note{}, store.ErrorEngineOperationError
	}

	note, err := decodeNote(value)
	if err != nil {
		return store.Note{}, store.ErrorEngineOperationError
	}
	return note, nil
}

func (rs *RedisStore) Update(note *store.Note) error {

	if err := rs.conn(); err != nil {
		log.Printf("redis conn failed: %s\n", err.Error())
		return store.ErrorEngineOperationError
	}

	_, err := rs.client.HGet(rs.StoreKey, id2field(note.Id)).Result()
	if err == redis.Nil {
		return store.ErrorNotFound
	} else if err != nil {
		log.Printf("[HGet] redis operation failed: %s\n", err.Error())
		return store.ErrorEngineOperationError
	}

	value, err := encodeNote(*note)
	if err != nil {
		return store.ErrorEngineOperationError
	}

	if err := rs.client.HSet(rs.StoreKey, id2field(note.Id), value).Err(); err != nil {
		log.Printf("[HSet] redis operation failed: %s\n", err.Error())
		return store.ErrorEngineOperationError
	}

	return nil
}

func (rs *RedisStore) Delete(id uint64) error {

	if err := rs.conn(); err != nil {
		log.Printf("redis conn failed: %s\n", err.Error())
		return store.ErrorEngineOperationError
	}

	if err := rs.client.HDel(rs.StoreKey, id2field(id)).Err(); err != nil {
		log.Printf("[HDel] redis operation failed: %s\n", err.Error())
		return store.ErrorEngineOperationError
	}

	return nil
}

func (rs *RedisStore) ListAll() ([]store.Note, error) {

	if err := rs.conn(); err != nil {
		log.Printf("redis conn failed: %s\n", err.Error())
		return []store.Note{}, store.ErrorEngineOperationError
	}

	var notes []store.Note
	values, err := rs.client.HGetAll(rs.StoreKey).Result()
	if err == redis.Nil {
		return []store.Note{}, store.ErrorNotFound
	} else if err != nil {
		log.Printf("[HGetALL] redis operation failed: %s\n", err.Error())
		return notes, store.ErrorEngineOperationError
	}

	for _, v := range values {
		note, err := decodeNote(v)
		if err != nil {
			return notes, store.ErrorEngineOperationError
		}
		notes = append(notes, note)
	}

	return notes, nil
}
